﻿/*
* Copyright (C) 2020 ~ 2024 chaigec All Rights Reserved.
*
* Author:     chaigec <chaigec@163.com>
* Maintainer: chaigec <chaigec@163.com>

 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
*/

#include "MkLog.h"
#include "MkUtil.h"
#include <stdarg.h>
#include "MkThread.h"
#include "MkQueue.h"
#include "MkFile.h"
#include "MkTime.h"

#define MaxLogSize   50000
#ifdef MkUt
#define MaxLogFileSize   (1024 * 400)  //4KB
#else
#define MaxLogFileSize   (1024 * 1024 * 500)  //500MB
#endif
#define DaySecond	(60*60*24)

#ifdef _WIN32
const Uint16 LevelColor[LOG_MAX] = { 0,
FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED ,
FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_GREEN ,
FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED ,
FOREGROUND_INTENSITY | FOREGROUND_RED };
Uint32 SetConsoleColor(Uint16 Color)
{
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    if (!handle) {
        return UnknownError;
    }
    SetConsoleTextAttribute(handle, Color);
    return  NoneError;
}
#endif

static CMkLog g_Instance;

CMkLog &CMkLog::GetInstance()
{
    return g_Instance;
}
CMkLog::CMkLog()
    : m_MaxSaveTime(30)
    , m_fpFile(nullptr)
    , m_LogQueue(1000)
    , m_bRun(FALSE)
    , m_FileWriteSize(0)
    , m_BackIndex(0)
    , m_CurDayTime(NowMkTime)
{
    m_strLogPath.clear();
    m_strModuleName.clear();
    m_WriteLevel = LOG_ERROR;
    m_WriteDst = TO_SCREEN;
}

Uint32 CMkLog::Initialize(const MkString &strModuleName, Uint32 dst/* = TO_SCREEN*/)
{
    m_strModuleName = strModuleName;
    if (TO_FILE & dst) {
        CMkFile::GetLogPath(m_strLogPath);
        if (!CMkFile::CheckFileIsExist(m_strLogPath)) {
            if (NoneError != CMkFile::CreateBasePath(m_strLogPath)) {
                printf("mkdir log path failed\n");
                return CreateFolderFailed;
            }
        }

        CMkTime CurDayTime = NowMkTime;
        GetLogFiles(CurDayTime);
        MkString strDayTime;
        CurDayTime.GetDayString(strDayTime);
        m_CurLogFile = m_strLogPath + "/" + m_strModuleName + "_" + strDayTime + ".log";
        m_FileWriteSize = CMkFile::GetFileSize(m_CurLogFile);
        m_MapLogFile[m_CurLogFile] = CurDayTime;
        m_fpFile = fopen(m_CurLogFile.c_str(), "a+");
        if (!m_fpFile) {
            return OpenFileFailed;
        }
    }
    m_WriteDst = dst;
    if (!m_bRun) {
        m_bRun = TRUE;
        m_LogThread.Start([this]() {
            Dolog();
        });
    }
    return NoneError;
}

Uint32 CMkLog::ChangeWriteLevel(MkLogLevel Level)
{
    m_WriteLevel = Level;
    return NoneError;
}

Uint32 CMkLog::GetWriteLevel(OUT MkLogLevel &Level)
{
    Level = m_WriteLevel;
    return NoneError;
}

Uint32 CMkLog::ChangeWriteDst(Uint32 Dst)
{
    m_WriteDst = Dst;
    return NoneError;
}

Uint32 CMkLog::GetWriteDst(OUT Uint32 &Dst)
{
    Dst = m_WriteDst;
    return NoneError;
}

Uint32 CMkLog::GetModuleName(OUT MkString &strModuleName)
{
    strModuleName = m_strModuleName;
    return NoneError;
}

Uint32 CMkLog::GetstrLevel(MkLogLevel Level, OUT MkString &strLevel)
{
    switch (Level) {
    case LOG_INFO:
        strLevel = "INFO";
        break;
    case LOG_DEBUG:
        strLevel = "DEBUG";
        break;
    case LOG_WARNING:
        strLevel = "WARN";
        break;
    default:
        strLevel = "ERROR";
        break;
    }
    return NoneError;
}
Uint32 CMkLog::GetSimpleFileName(const MkString &SoureFile, MkString &SimpleFile)
{
    size_t FindIndex = SoureFile.rfind("/");
    if (MkString::npos == FindIndex) {
        FindIndex = SoureFile.rfind("\\");
    }
    FindIndex += 1;
    SimpleFile = SoureFile.substr(FindIndex);
    return NoneError;
}

Uint32 CMkLog::WriteLog(MkLogLevel Level, const char *FileName, int Line, const char *format, ...)
{
    if (Level >= m_WriteLevel) {
        MkString Buffer;
        Buffer.resize(MaxLogSize, 0);
        MkString strTime;
        MkString strLevel;
        MkString SimpleFileName;
        Uint32 bufLen = 0;

        CMkTime NowTime = NowMkTime;
        Uint32 ErrorCode = NowTime.GetLogString(strTime);
        if (NoneError != ErrorCode) {
            return ErrorCode;
        }
        GetstrLevel(Level, strLevel);
        GetSimpleFileName(FileName, SimpleFileName);
        snprintf((Int8*)Buffer.data(), MaxLogSize, "%s [%s]-%u %s:%d: ", strTime.c_str(), strLevel.c_str(), CMkUtil::GetLocalThreadId(), SimpleFileName.c_str(), Line);
        bufLen = static_cast<Uint32>(strlen(Buffer.c_str()));

        va_list arg_ptr;
        va_start(arg_ptr, format);
        vsnprintf((Int8*)Buffer.data() + bufLen, MaxLogSize - bufLen, format, arg_ptr);
        va_end(arg_ptr);
        Buffer[MaxLogSize - 1] = '\0';

        if (TO_SCREEN & m_WriteDst) {
#ifdef _WIN32
            SetConsoleColor(LevelColor[Level]);
#endif
            printf("%s", Buffer.c_str());
        }

        if (TO_FILE & m_WriteDst) {
            m_LogQueue.Input(Buffer);
        }
    }
    return NoneError;
}

Uint32 CMkLog::WriteErrorCodeLog(MkLogLevel Level, const char *FileName, int Line, Uint32 ErrorCode, const char *format, ...)
{
    if (Level >= m_WriteLevel) {
        MkString Buffer;
        Buffer.resize(MaxLogSize, 0);
        MkString strTime;
        MkString strLevel;
        MkString SimpleFileName;
        Uint32 bufLen = 0;

        CMkTime NowTime = NowMkTime;
        Uint32 CurErrorCode = NowTime.GetLogString(strTime);
        if (NoneError != CurErrorCode) {
            return CurErrorCode;
        }
        GetstrLevel(Level, strLevel);
        GetSimpleFileName(FileName, SimpleFileName);
        snprintf((Int8*)Buffer.c_str(), MaxLogSize, "%s [%s:%d]-%u %s:%d: ", strTime.c_str(), strLevel.c_str(), ErrorCode, CMkUtil::GetLocalThreadId(), SimpleFileName.c_str(), Line);
        bufLen = static_cast<Uint32>(strlen(Buffer.c_str()));

        va_list arg_ptr;
        va_start(arg_ptr, format);
        vsnprintf((Int8*)Buffer.c_str() + bufLen, MaxLogSize - bufLen, format, arg_ptr);
        va_end(arg_ptr);
        Buffer[MaxLogSize - 1] = '\0';

        if (TO_SCREEN & m_WriteDst) {
#ifdef _WIN32
            SetConsoleColor(LevelColor[Level]);
#endif
            printf("%s", Buffer.c_str());
        }

        if (TO_FILE & m_WriteDst) {
            m_LogQueue.Input(Buffer);
        }
    }
    return NoneError;
}

Uint32 CMkLog::GetLogFiles(const CMkTime& NowTime)
{
    MkString strFormat = m_strLogPath + "/" + m_strModuleName;
#ifdef _WIN32
    strFormat += "_%04d_%02d_%02d_%d.zip";
#else
    strFormat += "_%04d_%02d_%02d_%d.tar";
#endif

    MkVectorString VecLogFile;
    CMkFile::ScanDir(m_strLogPath, VecLogFile);
    MkVectorString::iterator it = VecLogFile.begin();
    while (it != VecLogFile.end()) {
        if (MkString::npos == it->find(m_strModuleName)) {
            it = VecLogFile.erase(it);
        } else {
            int nYear = 0;
            int nMonth = 0;
            int nDay = 0;
            int Index = 0;
            int count = sscanf(it->c_str(), strFormat.c_str(), &nYear, &nMonth, &nDay, &Index);
            if (NowTime.GetYear() == nYear
                && NowTime.GetMonth() == nMonth
                && NowTime.GetDay() == nDay
                && Index > static_cast<int>(m_BackIndex)) {
                m_BackIndex = Index;
            }

            CMkTime FileTime(nYear, nMonth, nDay);
            if (NowMkTime.GetTvSec() - FileTime.GetTvSec() > m_MaxSaveTime * DaySecond) {
                CMkFile::DeleteLocalFile(*it);
            } else {
                m_MapLogFile[*it] = FileTime;
            }
            it++;
        }
    }
    return NoneError;
}

Uint32 CMkLog::WriteLogToFile(const MkString& LogString)
{
    CMkTime NowTime = NowMkTime;
    MkString strDayTime;
    if (m_FileWriteSize > MaxLogFileSize || NowTime.GetDay() != m_CurDayTime.GetDay()) {
        if (nullptr != m_fpFile) {
            fclose(m_fpFile);
        }

        m_CurDayTime.GetDayString(strDayTime);
        MkString BackUpFileName = m_strLogPath + "/" + m_strModuleName + "_" + strDayTime + "_" + std::to_string(++m_BackIndex);
#ifdef _WIN32
        BackUpFileName += ".zip";
#else
        BackUpFileName += ".tar";
#endif
        CMkFile::BackUpFile(m_CurLogFile, BackUpFileName);
        CMkFile::DeleteLocalFile(m_CurLogFile);
        m_MapLogFile[BackUpFileName] = m_CurDayTime;
        m_FileWriteSize = 0;

        m_CurDayTime = NowTime;
        NowTime.GetDayString(strDayTime);
        m_CurLogFile = m_strLogPath + "/" + m_strModuleName + "_" + strDayTime + ".log";
        m_MapLogFile[m_CurLogFile] = m_CurDayTime;
        m_fpFile = fopen(m_CurLogFile.c_str(), "a+");

        //delete file
        MkMap<MkString, CMkTime>::iterator itFile = m_MapLogFile.begin();
        while (itFile != m_MapLogFile.end()) {
            if (NowTime.GetTvSec() - itFile->second.GetTvSec() > m_MaxSaveTime*DaySecond) {
                CMkFile::DeleteLocalFile(itFile->first);
                itFile = m_MapLogFile.erase(itFile);
            } else {
                itFile++;
            }
        }
    }
    if (nullptr != m_fpFile) {
        m_FileWriteSize += strlen(LogString.c_str());
        fwrite(LogString.c_str(), 1, strlen(LogString.c_str()), m_fpFile);
        fflush(m_fpFile);
    }
    return NoneError;
}

void CMkLog::Dolog()
{
    MkString Logs;
    while (m_bRun) {
        m_LogQueue.Output(Logs, 0xFFFFFFFF);
        if (Logs.empty()) {
            break;
        }
        WriteLogToFile(Logs);
    }
}

CMkLog::~CMkLog()
{
    m_strLogPath.clear();
    m_strModuleName.clear();
    m_WriteLevel = LOG_ERROR;
    m_WriteDst = TO_SCREEN;
    m_bRun = FALSE;
    for (int i = 0; i < 10; i++) {
        m_LogQueue.Input("");
    }

    m_LogThread.Stop();
    if (nullptr != m_fpFile) {
        fflush(m_fpFile);
        fclose(m_fpFile);
    }
}
